package com.xyzwps.orm.jpa.repository;

import com.xyzwps.orm.exception.NoSuchTagException;
import com.xyzwps.orm.exception.TagDuplicateException;
import com.xyzwps.orm.model.Tag;
import com.xyzwps.orm.repository.TagRepository;
import com.xyzwps.orm.util.ExceptionUtils;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

public class JpaTagRepository implements TagRepository {

    private final EntityManager em;

    public JpaTagRepository(EntityManager em) {
        this.em = Objects.requireNonNull(em);
    }

    @Override
    public int insert(Tag tag) {
        var tx = em.getTransaction();
        tx.begin();
        Exception exception = null;
        try {
            em.persist(tag);
            em.flush();
        } catch (Exception ex) {
            exception = ex;
        }

        if (exception == null) {
            tx.commit();
            return tag.getTagId();
        } else {
            tx.rollback();
            if (ExceptionUtils.causedBy(exception, SQLIntegrityConstraintViolationException.class)) {
                throw new TagDuplicateException(tag.getTagName());
            } else {
                throw new IllegalStateException(exception);
            }
        }
    }

    @Override
    public void update(Tag tag) {
        var tx = em.getTransaction();
        tx.begin();
        var pTag = this.selectManagedByTagId(tag.getTagId());
        if (pTag == null) {
            tx.rollback();
            return;
        }

        Exception exception = null;
        try {
            pTag.setUserId(tag.getUserId());
            pTag.setTagName(tag.getTagName());
            em.flush();
        } catch (Exception ex) {
            exception = ex;
        }
        if (exception == null) {
            tx.commit();
        } else {
            tx.rollback();
            if (ExceptionUtils.causedBy(exception, SQLIntegrityConstraintViolationException.class)) {
                throw new TagDuplicateException(tag.getTagName());
            } else {
                throw new IllegalStateException(exception);
            }
        }
    }

    @Override
    public void delete(int tagId) {
        var tx = em.getTransaction();
        tx.begin();
        var jpql = "delete from Tag t where t.tagId = :tagId";
        em.createQuery(jpql).setParameter("tagId", tagId).executeUpdate();
        tx.commit();
    }

    /**
     * 这里按 tagId 查询 {@link Tag} 时，没有使用 {@link EntityManager#find(Class, Object)} 方法。
     * 原因是，在使用 JPQL 按 tagId 删除 {@link Tag} 后，此 tagId 对应的 tag 还在 EntityManager
     * 中缓存着，用 find 方法还是能查到。真坑
     */
    @Override
    public Optional<Tag> selectByTagId(int tagId) {
        var tag = selectManagedByTagId(tagId);
        if (tag == null) {
            return Optional.empty();
        }
        return Optional.of(tag.deepCopy()); // 深复制，不想要 managed entity
    }

    private Tag selectManagedByTagId(int tagId) {
        try {
            var jpql = "select t from Tag t where t.tagId = :tagId";
            return em.createQuery(jpql, Tag.class).setParameter("tagId", tagId).getSingleResult();
        } catch (NoResultException ex) {
            return null;
        }
    }

    @Override
    public List<Tag> selectAllForUser(int userId) {
        var jpql = "select t from Tag t where t.userId = :userId";
        return em.createQuery(jpql, Tag.class)
                .setParameter("userId", userId)
                .getResultList()
                .stream().map(Tag::deepCopy) // 深复制，不想要 managed entity
                .collect(Collectors.toList());
    }
}
